package com.ming_za.odupdaterserver.core.utils.account.param;

import com.ming_za.odupdaterserver.core.utils.account.AccountRE;
import com.ming_za.odupdaterserver.core.utils.account.api.TokenFactory;
import com.ming_za.odupdaterserver.core.utils.account.exception.TokenGenerateException;
import com.ming_za.odupdaterserver.core.utils.account.preparatory.TokenType;
import com.ming_za.odupdaterserver.core.utils.account.token.TokenData;
import com.ming_za.odupdaterserver.core.utils.account.token.TokenPackage;
import lombok.Data;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class TokenManager {

    /**
     * token工厂，用于接入创建和解读token的代码
     */
    private final TokenFactory tokenFactory;

    /**
     * token过期时间，单位：ms
     */
    private final long tokenExpire;

    /**
     * 续签时间
     * <p>如果为0，表示不开启续签</p>
     * <p>如果不为0，表示最大续签的时间，当验证token时剩余时间小于这个值，将重新生成token</p>
     * <p>该值不能大于等于{@link #tokenExpire}</p>
     */
    private final long renewalTime;

    /**
     * token缓存，验证成功的token将会暂时保存于此
     */
    private final Map<Long, TokenCache> tokenCache = new ConcurrentHashMap<>();

    /**
     * 创建token管理器
     * @param tokenFactory token工厂实现类,传入已有的{@link TokenFactory}实现类或者自行编写实现类
     * @param tokenType
     * @return
     */
    public static TokenManager createTokenManager(TokenFactory tokenFactory,TokenType tokenType) {
        return createTokenManager(tokenFactory,tokenType.getTokenExpire(),tokenType.getRenewalTime());
    }

    /**
     * 创建token管理器
     * @param tokenFactory token工厂实现类,传入已有的{@link TokenFactory}实现类或者自行编写实现类
     * @param tokenExpire token有效时间，单位：ms
     * @param renewalTime token续签时间，单位：ms，传入0表示不开启续签
     */
    public static TokenManager createTokenManager(TokenFactory tokenFactory, long tokenExpire, long renewalTime) {
        return new TokenManager(tokenFactory, tokenExpire, renewalTime);
    }

    private TokenManager(TokenFactory tokenFactory, long tokenExpire, long renewalTime) {
        Thread thread = new Thread(getTokenCacheRunnable(), "TokenManager");
        thread.setDaemon(true);//守护线程，跟随主线程结束
        thread.start();
        this.tokenFactory = tokenFactory;
        this.tokenExpire = tokenExpire;
        this.renewalTime = renewalTime;
        if (renewalTime >= tokenExpire){
            throw new RuntimeException("续签时间不能 >=token过期时间");
        }
    }

    /**
     * 创建token
     * @param account 账号
     * @return token
     */
    public TokenPackage createToken(long account) throws TokenGenerateException {

        long now = System.currentTimeMillis();
        TokenPackage tp = tokenFactory.createToken(new TokenData(account,now),tokenExpire);
        if (tp == null){
            throw new TokenGenerateException("token生成失败");
        }
        putTokenCache(account,now,tp.getToken());
        tryRefreshCache();
        return tp;
    }

    /**
     * 检测token
     * @param account 账号
     * @param token token
     * @param key 密钥
     * @return 当开启token续签时，验证成功将返回新的token
     */
    public ReturnAccountPackage<TokenPackage> checkToken(long account, String token, byte[] key) {

        if (key == null){
//            throw new TokenFailureException("token验证失败");
            return new ReturnAccountPackage<>(false, AccountRE.TOKEN_FAILURE,null);
        }

        TokenCache tc = tokenCache.get(account);

        long now = System.currentTimeMillis();
        long tokenRemainTime;

        if (tc != null) {
            //当存在缓存时，使用缓存token进行验证
            tokenRemainTime = tokenExpire - (now - tc.createTime);
            //续签前的token仍可以使用到有效期结束
            if ((!tc.token.equals(token) &&
                    (tc.beforeRenewalToken == null || !tc.beforeRenewalToken.equals(token) || now - tc.beforeRenewalTime > tokenExpire))
                    || (tokenRemainTime < 0)){
//                throw new TokenFailureException("token验证失败");
                return new ReturnAccountPackage<>(false, AccountRE.TOKEN_FAILURE,null);
            }

        }else {
            //当缓存中不存在token时通过token和key解码验证
            TokenData tokenData = tokenFactory.decodeToken(account,token, key,tokenExpire);

            if (tokenData == null) {
//                throw new TokenFailureException("token验证失败");
                return new ReturnAccountPackage<>(false, AccountRE.TOKEN_FAILURE,null);
            }

            tokenRemainTime = tokenExpire - (now - tokenData.getTime());

            putTokenCache(account,tokenData.getTime(),token);
        }

        TokenPackage returnValue = null;

        //续签操作
        if (renewalTime != 0 && tokenRemainTime < renewalTime){
            TokenPackage tp = tokenFactory.createToken(new TokenData(account,now),tokenExpire);
            if (renewalToken(account,token,now)){
                returnValue = tp;
            }
        }

        tryRefreshCache();

//        return returnValue;
        return new ReturnAccountPackage<>(true, AccountRE.SUCCESS,returnValue);
    }

    /**
     * 续签token
     * @param account 账号
     * @param token token
     * @param time 时间
     * @return 成功返回true，否则返回false
     */
    private boolean renewalToken(long account,String token,long time) {
        TokenCache tc = tokenCache.get(account);
        if (tc != null) {
            synchronized (tc) {
                //避免并发修改，只修改符合续签时间的一项
                if (tokenExpire - (System.currentTimeMillis() - tc.createTime) < renewalTime){
                    tc.renewalToken(token,time);
                    return true;
                }
            }
        }else {
            //如果找不到则创建新缓存
            putTokenCache(account,time,token);
            return true;
        }
        return false;
    }

    private void putTokenCache(long account,long time,String token){
        tokenCache.put(account,new TokenCache(time,token));
    }

    /**
     * 尝试刷新异步刷新token缓存
     * <p>最短刷新时长：10s，避免频繁刷新消耗性能</p>
     */
    private void tryRefreshCache(){
        long now = System.currentTimeMillis();
        long lcct = lastClearCacheTime;
        if (now - lastClearCacheTime >= 10000){
            synchronized (tokenCache){
                if (lcct == lastClearCacheTime){
                    tokenCache.notify();
                }
            }
        }
    }

    private volatile long lastClearCacheTime = 0;

    private Runnable getTokenCacheRunnable() {
        return ()->{
            synchronized (tokenCache){
                while (!Thread.currentThread().isInterrupted()) {

                    //当前时间
                    lastClearCacheTime = System.currentTimeMillis();
                    for (Map.Entry<Long, TokenCache> entry : tokenCache.entrySet()) {
                        TokenCache cache = entry.getValue();
                        if (lastClearCacheTime - cache.createTime > tokenExpire){
                            //主token过期
                            tokenCache.remove(entry.getKey());

                        }else if (lastClearCacheTime - cache.beforeRenewalTime > tokenExpire){
                            //续签前token过期
                            cache.clearRenewal();
                        }
                    }
                    //清理完成后等待线程
                    try {
                        tokenCache.wait();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
            }
        };
    }

    @Data
    static class TokenCache{
        /**
         * token创建时间
         */
        private long createTime;
        /**
         * 最新token
         */
        private String token;

        /**
         * 续签前有效期
         */
        private long beforeRenewalTime;
        /**
         * 续签前token
         */
        private String beforeRenewalToken;

        TokenCache(long createTime, String token) {
            this.createTime = createTime;
            this.token = token;
            this.beforeRenewalToken = null;
        }

        /**
         * 清除续签
         */
        public void clearRenewal(){
            this.beforeRenewalTime = 0;
            this.beforeRenewalToken = null;
        }

        public void renewalToken(String token,long time){
            this.beforeRenewalTime = this.createTime;
            this.createTime = time;
            this.beforeRenewalToken = this.token;
            this.token = token;
        }
    }
}
